Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block based on response status or headers #171

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

cataphract
Copy link
Contributor

@cataphract cataphract commented Feb 19, 2025

Significantly changes how the hooking mechanism for executing the final WAF run. The purpose is to allow blocking based on the response headers and status that would otherwise be sent.

Previously, we would install only an output filter. By the time it was invoked, the headers would already have been committed. It would not be possible to fully replace the response with another one.

So this PR installs also a header filter. When invoked, this header filter prevents the final filter from committing the response by changing the send_chain function pointer, continuing the filter chain and saving the buffers instead (because the filter may not invoke send_chain, it needs also to look at where this data may be buffered: this needs different code for http 1/3 and http/2).

Afterwards, it launches the WAF task. While this task is running, any data send to the output buffer is consumed as saved (copied), up to a certain limit. Once this limit is reached, the buffers are not consumed anymore, therefore stalling the output chain with busy buffers.

Once the final WAF task finishes, either a blocking response is committed, fully replacing the headers and body, or, if we're not to block, the buffered headers and body are sent forward (with special logic wrt the headers in http/2, as ngx_http_write_filter() can't send header frames in http/2).

Other changes:

  • Blocking tests in http/2 and /3
  • Fix read out-of-bounds in ngx_header_writer.h

@cataphract cataphract requested review from a team as code owners February 19, 2025 17:03
@cataphract cataphract requested review from dubloom and removed request for a team February 19, 2025 17:03
elif http_version == 3:
version_arg = "--http3-only"
else:
raise Exception(f"Unknown HTTP version: {http_version}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

Exception is too generic (...read more)

Do not raise Exception and BaseException. These are too generic. Having generic exceptions makes it difficult to differentiate errors in a program. Use a specific exception, for example, ValueError, or create your own instead of using generic ones.

Learn More

View in Datadog  Leave us feedback  Documentation

@@ -1,9 +1,10 @@
from alpine:3.19
FROM alpine/curl-http3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Quality Violation

always pin version to an image spec (...read more)

This rule dictates that Docker images should always be tagged with a specific version number. In Docker, an image tag represents a particular version of an image. The use of tags allows developers to have better control over which versions of an image are being used in their projects.

This is crucial because it ensures the consistency and reliability of the Docker environment. If an image is not tagged, Docker defaults to using the 'latest' version of the image. However, the 'latest' tag does not guarantee that the same version of an image will be used every time, which can lead to unexpected behavior or compatibility issues.

To comply with this rule, always specify a version number when pulling a Docker image. Instead of FROM debian, write FROM debian:unstable or FROM debian:10.3. This ensures that you are using a specific version of the image, providing a more predictable and stable environment for your project.

View in Datadog  Leave us feedback  Documentation

Looks unnecessary once the full header filter chain is run.
@dmehala
Copy link
Contributor

dmehala commented Feb 20, 2025

@cataphract We're not part of the same team or even the same organization. Given that, providing some context upfront can help set the stage for those reviewing your PR. A bit of background on what it does and why it’s needed would go a long way in ensuring a smoother review process. Could you, please, share more details?

@codecov-commenter
Copy link

codecov-commenter commented Feb 21, 2025

Codecov Report

Attention: Patch coverage is 65.01767% with 198 lines in your changes missing coverage. Please review.

Project coverage is 71.25%. Comparing base (f1e5aff) to head (7cfce28).

Files with missing lines Patch % Lines
src/security/context.cpp 62.88% 166 Missing and 27 partials ⚠️
src/security/blocking.cpp 77.27% 3 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #171      +/-   ##
==========================================
- Coverage   71.87%   71.25%   -0.62%     
==========================================
  Files          44       44              
  Lines        6190     6683     +493     
  Branches      899      948      +49     
==========================================
+ Hits         4449     4762     +313     
- Misses       1305     1466     +161     
- Partials      436      455      +19     
Files with missing lines Coverage Δ
src/datadog_context.cpp 61.79% <100.00%> (+4.22%) ⬆️
src/ngx_header_writer.h 93.02% <100.00%> (+0.16%) ⬆️
src/ngx_http_datadog_module.cpp 60.51% <ø> (ø)
src/security/blocking.h 100.00% <ø> (ø)
src/security/context.h 82.35% <ø> (ø)
src/security/library.cpp 61.75% <100.00%> (+0.93%) ⬆️
src/security/library.h 100.00% <ø> (ø)
src/security/blocking.cpp 80.98% <77.27%> (-0.12%) ⬇️
src/security/context.cpp 65.83% <62.88%> (-3.26%) ⬇️

@cataphract
Copy link
Contributor Author

cataphract commented Feb 21, 2025

@cataphract We're not part of the same team or even the same organization. Given that, providing some context upfront can help set the stage for those reviewing your PR. A bit of background on what it does and why it’s needed would go a long way in ensuring a smoother review process. Could you, please, share more details?

@dmehala I've added a description now. It was meant to be a draft PR; I opened it to run the pipelines.

try {
const urlObj = new URL(request.url, `http://${request.headers.host}`);
const card = parseInt(urlObj.searchParams.get("card"));
const numBouts = parseInt(urlObj.searchParams.get("num_bouts"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

Missing radix parameter. (...read more)

When utilizing the parseInt() function, many often skip the second parameter (the radix), allowing the function to deduce the number type based on the initial argument. By default, parseInt() can recognize both decimal and hexadecimal numbers, the latter through the 0x prefix. However, before ECMAScript 5, the function also mistakenly recognized octal numbers, leading to issues as many developers presumed a starting 0 would be disregarded.

View in Datadog  Leave us feedback  Documentation

const urlObj = new URL(request.url, `http://${request.headers.host}`);
const card = parseInt(urlObj.searchParams.get("card"));
const numBouts = parseInt(urlObj.searchParams.get("num_bouts"));
const delay = parseInt(urlObj.searchParams.get("delay"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

Missing radix parameter. (...read more)

When utilizing the parseInt() function, many often skip the second parameter (the radix), allowing the function to deduce the number type based on the initial argument. By default, parseInt() can recognize both decimal and hexadecimal numbers, the latter through the 0x prefix. However, before ECMAScript 5, the function also mistakenly recognized octal numbers, leading to issues as many developers presumed a starting 0 would be disregarded.

View in Datadog  Leave us feedback  Documentation

const sendRepeatResponse = (request, response) => {
try {
const urlObj = new URL(request.url, `http://${request.headers.host}`);
const card = parseInt(urlObj.searchParams.get("card"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

Missing radix parameter. (...read more)

When utilizing the parseInt() function, many often skip the second parameter (the radix), allowing the function to deduce the number type based on the initial argument. By default, parseInt() can recognize both decimal and hexadecimal numbers, the latter through the 0x prefix. However, before ECMAScript 5, the function also mistakenly recognized octal numbers, leading to issues as many developers presumed a starting 0 would be disregarded.

View in Datadog  Leave us feedback  Documentation

Copy link
Contributor

@Anilm3 Anilm3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From reading the PR it's quite hard to ascertain if this will work as intended, at least not without spending a similar amount of time it took to write it reviewing it... However aside from a few comments, I don't see any critical issues although some mistakes have been highlighted and should be fixed.

Comment on lines 1249 to 1252
assert(req != nullptr);

auto *ctx = instance.current_ctx_;
assert(ctx != nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How certain are we about these assertions...? An explicit check may be more prudent otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's not clear is that c->data is the request in http3.

Seeing as this function is very difficult to exercise and the tests don't reach it (the data for the headers is small enough that it isn't sent just yet), I'm changing this to not rely on c->data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants